#include "Http.hpp"

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/select.h>

#include <iostream>
#include <cassert>


using namespace std;

Http::Http()
    :mTimeOut(DEFAULT_TIME_OUT) {}

// the url should have been parsed
bool
Http::FetchUrl(const Url& rUrl, Page& rPage)
{
    int sock, ret;
    //     assert(!rUrl.mIP.empty());
    if (rUrl.mIP.empty()) {
        cout << "\tNOTE: Can not find ip address for " << rUrl.mHost << endl;
        return false;
    }
    
    sock = this->MakeSocket(rUrl.mIP, rUrl.mPort);

    if (sock == -1 || sock == 0) {
        return false;
    }

    if (!this->MakeRequest(sock, rUrl.mHost.c_str(), rUrl.mPath.c_str(), false)) {
        close(sock);
        return false;
    }
    
    char* p_header = new char[HEADER_BUF_SIZE];
    ret = this->ReadHeader(sock, p_header);
    if (ret == -1) {
        close(sock);
        return false;
    } else {
        rPage.SetHeader(p_header);
    }
    
    if (!rPage.ParseHeaderInfo()) {
        close(sock);
        return false;
    }

    char* p_content = new char[rPage.GetContentLength() + 1];
    ret = this->ReadBody(sock, p_content, rPage.GetContentLength());
    if (ret == -1) {
        close(sock);
        return false;
    } else {
        rPage.SetContent(p_content);
    }
    
    close(sock);
    return true;
}

// bool
// Http::FetchUrlPersist(const Url& crUrl, Page &rPage, int &sock)
// {
//     int ret;

//     if (sock==-1 || !this->MakeRequest(sock, crUrl.mHost, crUrl.mPath, true)) {
//         // 连接错误重新建立
// #ifdef DEBUG
//         cerr << "Rebuild the socket..." << endl;
// #endif
//         sock = this->MakeSocket(crUrl.mHost, crUrl.mPort);
//         if (!this->MakeRequest(sock, crUrl.mHost, crUrl.mPath, true)) {
//             // 再次连接出错
//             return false;
//         }
//     }

//     char* p_header = new char[HEADER_BUF_SIZE];
//     ret = this->ReadHeader(sock, p_header);
//     if (ret == -1)
//         return false;
//     else
//         rPage.SetHeader(p_header);

//     rPage.ParseHeaderInfo();
//     char* p_content = new char[rPage.GetContentLength() + 1];
//     ret = this->ReadBody(sock, p_content, rPage.GetContentLength());
//     if (ret == -1)
//         return false;
//     else
//         rPage.SetContent(p_content);

//     return true;
// }

void
Http::SetTimeOut(int timeOut)
{
    this->mTimeOut = timeOut;
}

int
Http::MakeSocket(const string& crIP, int port)
{
    int                sock;    // Socket descriptor
    int                ret;
    struct sockaddr_in sa;      // Socket address
    unsigned long      inaddr;
    int                flags;
    int                status;
    fd_set             mask;
    struct timeval     timeout;

    //    struct hostent     *hp;     // Host entity

    //     hp = gethostbyname(pHost.c_str());
    //     if(hp == NULL) {
    //         return -1;
    //     }

    // Copy host address from hostent to (server) socket address
    //     memcpy((char *)&sa.sin_addr, (char *)hp->h_addr, hp->h_length);
    //     sa.sin_family = hp->h_addrtype; // Set service sin_family to PF_INET
    //     sa.sin_port   = htons(port);    // Put portnum into sockaddr

    sa.sin_family = AF_INET;
    sa.sin_port   = htons(port);

    inaddr = (unsigned long) inet_addr(crIP.c_str());
    memcpy((char* )&sa.sin_addr, (char* )&inaddr, sizeof(inaddr));

    sock = socket(AF_INET, SOCK_STREAM, 0);
    if(sock == -1) {
        return -1;
    }

    flags = fcntl (sock, F_GETFL, 0);
    if (flags < 0) {
        return -1;
    }
    flags |= O_NONBLOCK;

    if (fcntl (sock, F_SETFL, flags) < 0) {
        return -1;
    }

    // 一般直接返回的是 "-1"
    ret = connect(sock, (struct sockaddr *)&sa, sizeof(struct sockaddr));
    if (ret == 0) {
        flags &= ~O_NONBLOCK;
        fcntl (sock, F_SETFL, flags);
        return sock;		//connected immediately
    }
    
    FD_ZERO (&mask);
    FD_SET (sock, &mask);
    timeout.tv_sec = mTimeOut;
    timeout.tv_usec = 0;
    status = select (sock + 1, NULL, &mask, NULL, &timeout);

    switch (status)
    {
    case -1:                 // Select error, set the socket as default blocking
        flags &= ~O_NONBLOCK;
        fcntl (sock, F_SETFL, flags);
        return -1;
    case 0:                     //Connection timed out.
        flags &= ~O_NONBLOCK;
        fcntl (sock, F_SETFL, flags);
        return -1;
    default:                    // Connected successfully.
        FD_CLR (sock, &mask);
        flags &= ~O_NONBLOCK;
        fcntl (sock, F_SETFL, flags);
        return sock;
    }
    return sock;
}

bool
Http::MakeRequest(int sock, const string& crHost, const string& crResPath, bool isPersist)
{
    bool  is_good_status = true;
    char* request_buffer = new char[DEFAULT_REQUEST_BUFFER_SIZE];

    request_buffer[0] = 0;   // 小技巧，长为0的字串

    // HTTP 1.0
    if (!crResPath.empty()) {
        snprintf(request_buffer, 1024, "GET %s %s\r\n", crResPath.c_str(), HTTP_VERSION);
    } else {
        snprintf(request_buffer, 1024, "GET %s\r\n", HTTP_VERSION);
    }

    strcat(request_buffer, "Host: ");
    strcat(request_buffer, crHost.c_str());
    strcat(request_buffer, "\r\n");
    strcat(request_buffer, "User-Agent: ");
    strcat(request_buffer, "HTTP-Fether");
    strcat(request_buffer, "/");
    strcat(request_buffer, "1.0");
    strcat(request_buffer, "\r\n");
    strcat(request_buffer, "Accept: */*");
    strcat(request_buffer, "\r\n");

    if (isPersist) {
        strcat(request_buffer, "Connection: Keep-Alive\r\n\r\n");
    } else {
        strcat(request_buffer, "Connection: Close\r\n\r\n");
    }
    printf("%s", request_buffer);

    if (write(sock, request_buffer, strlen(request_buffer)) == -1) {
        close(sock);
        cerr << "Write socket failed!" << endl;
        is_good_status = false;
    }
    delete [] request_buffer;
    return is_good_status;
}



int
Http::ReadHeader(int sock, char *pHeadPtr)
{
    struct timeval tv;
    fd_set         rfds;
    int            bytesRead = 0, newlines = 0, ret, selectRet;

    while(newlines != 2 && bytesRead != HEADER_BUF_SIZE) {
        FD_ZERO(&rfds);
        FD_SET(sock, &rfds);
        tv.tv_sec = this->mTimeOut;
        tv.tv_usec = 0;

        if(this->mTimeOut >= 0)
            selectRet = select(sock+1, &rfds, NULL, NULL, &tv);
        else                    // No timeout, can block indefinately
            selectRet = select(sock+1, &rfds, NULL, NULL, NULL);

        if(selectRet == 0 || selectRet == -1) {
            return false;
        }

        ret = read(sock, pHeadPtr, 1);
        if(ret == -1) {
            return false;
        }
        ++bytesRead;

        if(*pHeadPtr == '\r') { /* Ignore CR */
            /* Basically do nothing special, just don't set newlines to 0 */
            ++pHeadPtr;
            continue;
        }
        else if(*pHeadPtr == '\n') /* LF is the separator */
            newlines++;
        else
            newlines = 0;

        ++pHeadPtr;
    }

    pHeadPtr -= 3;              /* Snip the trailing LF's */
    *pHeadPtr = '\0';

    return bytesRead;
}

int
Http::ReadBody(int sock, char *pBodyPtr, int bodyLen)
{
    struct timeval tv;
    fd_set         rfds;
    int            bytes_read = 0;
    int            ret = 0;
    int            select_ret;

    if (bodyLen == 0) {
        bodyLen = DEFAULT_PAGE_SIZE;        // not good
        delete pBodyPtr;
        pBodyPtr = new char[DEFAULT_PAGE_SIZE + 1];
    }

    do {
        FD_ZERO(&rfds);
        FD_SET(sock, &rfds);
        tv.tv_sec = this->mTimeOut;
        tv.tv_usec = 0;

        if(this->mTimeOut >= 0)
            select_ret = select(sock+1, &rfds, NULL, NULL, &tv);
        else                    /* No timeout, can block indefinately */
            select_ret = select(sock+1, &rfds, NULL, NULL, NULL);

        if(select_ret == 0 || select_ret == -1) {
            close(sock);
            return -1;
        }

        ret = read(sock, pBodyPtr+bytes_read, bodyLen);
        bodyLen -= ret;
        bytes_read += ret;
    } while (ret > 0);

    pBodyPtr[bytes_read] = '\0';
    return bytes_read;
}

